<cfcomponent>
	
	<cfset this.component = 0 />
	
	<cffunction name="handleXmlRpc" access="remote" hint="I configure and return this xmlrpc object." output="false" returntype="void">
		
		<!--- <cfargument name="component" hint="I am the component to enable rpc calls on" required="yes" type="any" /> --->
		<!--- <cflog text="Request: #GetHTTPRequestData().content#" /> --->
		<cftry>
			<cfif CGI.REQUEST_METHOD IS "POST">
				<cfif StructKeyExists(GetHTTPRequestData(), "content")>
					<cfif IsXml(GetHTTPRequestData().content)>
						<cfset xmlrpc(XmlParse(GetHTTPRequestData().content), this.component) />
					<cfelse>
						<!--- we need to try to fix the XML in the request.  to do this, we're going to look for all the <string></string> tags.  we're going to extract the values in there and replace all > with &gt; --->
						<!--- <cflog text="Request does not contain XML: #GetHTTPRequestData().content#" /> --->
						<cfset xmlrpc(XmlParse(matchRegex(GetHTTPRequestData().content, "(?m)(<string>)(.*?)(</string>)", fixXml)), this.component) />
					</cfif>
				<cfelse>
					<!--- <cflog text="HTTP request does not contain content" /> --->
				</cfif>
			<cfelse>
				<!--- <cflog text="Request is not a post" /> --->
			</cfif>	
			<cfcatch>
				<!--- <cflog text="#GetHTTPRequestData().content#" /> --->
				<!--- <cflog text="Uncaught Error: #cfcatch.message#; #cfcatch.detail#" /> --->
				<cfrethrow />
			</cfcatch>
		</cftry>	
	</cffunction>
	
	<cffunction name="fixXml" access="private" hint="I fix a string value in an xml document" output="false" returntype="string">
		<cfargument name="match" />
	    <cfreturn "<string>#replace(arguments.match[3].string, '>', '&gt;', 'all')#</string>" />
	</cffunction>
	
	<cffunction name="xmlrpc" access="private" hint="I am a method which translates xmlrpc requests to method calls on a CFC" returntype="string">
		<cfargument name="rpc" hint="I am the xml data for the RPC" required="yes" type="xml" />
		<cfargument name="component" hint="I am the component to call methods on" required="yes" type="any" />
		<cfset var result = 0 />
		<cfset var methodName = findMethodNameUsingMetadata(arguments.rpc.methodCall.methodName.XmlText, getMetadata(arguments.component)) />
		<cfset var params = findMethodParams(arguments.rpc, arguments.component, methodName) />
		<cfset var x = 0 />
		<cfset var errorNum = -1 />
		
		<!--- make sure the method being called is remote --->

		<cftry>
			<!--- <cflog text="Calling Method: #methodName# (#arguments.rpc.methodCall.methodName.XmlText#)**" /> --->
			<cfinvoke component="#arguments.component#" method="#methodName#" returnvariable="result">
				<cfloop from="1" to="#ArrayLen(params)#" index="x">
					<cfinvokeargument name="#params[x].name#" value="#params[x].value#" />
				</cfloop>
			</cfinvoke>
			
			<cfset createSuccessRespose(result, findMethodReturnType(arguments.component, methodName)) />
			
			<cfcatch>
				<!--- <cflog text="Caught Error: #cfcatch.Message#; #cfcatch.detail#" /> --->
				<cfif IsDefined("cfcatch.ErrNumber")>
					<cfset errorNum = cfcatch.ErrNumber />
				</cfif>
				<cfset createFaultRespose(errorNum, "#cfcatch.toString()#") />
			</cfcatch>
		</cftry>
		
	</cffunction>
	
	<cffunction name="createSuccessRespose" access="private" hint="I respones to the remote request with a successful response document" output="false" returntype="any">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="any" />
		<cfargument name="hint" hint="I am a hint to pass to the serilizer." required="yes" type="string" />
		<cfset var result = 0 />
		
		<cfoutput><cfsavecontent variable="result"><?xml version="1.0"?>
			<methodResponse>
				<params>
					<param>
						#serialize(arguments.data, arguments.hint)#
					</param>
				</params>
			</methodResponse>			
		</cfsavecontent></cfoutput>
		
		<!--- <cflog text="Result: #result#" /> --->
		
		<cfoutput>
			<cfcontent reset="yes" />#result#
		</cfoutput>
		<cfabort />
	</cffunction>
	
	<cffunction name="createFaultRespose" access="private" hint="I respones to the remote request with a successful response document" output="false" returntype="any">
		<cfargument name="code" hint="I am the code" required="yes" type="numeric" />
		<cfargument name="message" hint="I am the message" required="yes" type="string" />
		<cfset var result = 0 />
		
		<cfoutput><cfsavecontent variable="result"><?xml version="1.0"?>
				<methodResponse>
					<fault>
						<value>
							<struct>
								<member>
									<name>faultCode</name>
									<value><int>#arguments.code#</int></value>
								</member>
								<member>
									<name>faultString</name>
									<value><string>#arguments.message#</string></value>
								</member>
							</struct>
						</value>
					</fault>
				</methodResponse>
		</cfsavecontent></cfoutput>
		
		<!--- <cflog text="Error: #result#" /> --->
		
		<cfoutput>
			<cfcontent reset="yes" />#result#
		</cfoutput>
		<cfabort />
	</cffunction>
	
	<cffunction name="serialize" access="private" hint="I translate CF data into rpc data" output="true" returntype="string">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="any" />
		<cfargument name="hint" hint="I am a hint on the datatype" required="no" type="string" default="" />
		<cfset var result = 0 />
		
		<cfsavecontent variable="result">
			<value>
				<cfif arguments.hint IS NOT "any">
					<!--- we have a hint of the datatype --->
					<cfswitch expression="#arguments.hint#">
						<cfcase value="numeric">
							<cfif Find(".", arguments.data)>
								<double>#arguments.data#</double>
							<cfelse>
								<int>#arguments.data#</int>
							</cfif>
						</cfcase>
						
						<cfcase value="boolean">
							<boolean>#iif(arguments.data, De(1), De(0))#</boolean>
						</cfcase>
						
						<cfcase value="string">
							<string>#XMLFormat(arguments.data)#</string>				
						</cfcase>
						
						<cfcase value="date">
							<dateTime.iso8601>#DateFormat(arguments.data, "yyyymmdd") & "T" & TimeFormat(arguments.data, "HH:mm:ss")#</dateTime.iso8601>
						</cfcase>
						
						<cfcase value="binary">
							<base64>#ToBase64(arguments.data)#</base64>
						</cfcase>
					</cfswitch>
					
				<cfelseif IsStruct(arguments.data)>
					#serializeStruct(arguments.data)#
					
				<cfelseif IsArray(arguments.data)>
					#serializeArray(arguments.data)#
					
				<cfelse>
					<cfif IsNumeric(arguments.data)>
						<cfif Find(".", arguments.data)>
							<double>#arguments.data#</double>
						<cfelse>
							<int>#arguments.data#</int>
						</cfif> 
						
					<cfelseif IsDate(arguments.data)>
						<dateTime.iso8601>#DateFormat(arguments.data, "yyyymmdd") & "T" & TimeFormat(arguments.data, "HH:mm:ss")#</dateTime.iso8601>

					<cfelseif IsBinary(arguments.data)>
						<base64>#ToBase64(arguments.data)#</base64>

					<cfelseif IsSimpleValue(arguments.data)>
						<string>#XMLFormat(arguments.data)#</string>
						
					<cfelse>
						<cfthrow message="Unrecognized Datatype" detail="The xmlrpc component doesn't support this type of data, whatever the heck it is." type="xmlrpc.UnrecognizedDatatype" />

					</cfif>
					
				</cfif>
			</value>
		</cfsavecontent>
		
		<cfreturn result />
	</cffunction>
	
	<cffunction name="serializeStruct" access="private" hint="I translate a CF strct into an rpc struct" output="true" returntype="string">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="struct" />
		<cfset var result = 0 />
		<cfset var item = 0 />
		
		<cfsavecontent variable="result">
			<struct>
				<cfloop collection="#arguments.data#" item="item">
					<member>
						<name>#item#</name>
						#serialize(arguments.data[item], "any")#
					</member>
				</cfloop>
			</struct>
		</cfsavecontent>
		
		<cfreturn result />
	</cffunction>
		
	<cffunction name="serializeArray" access="private" hint="I translate CF array into an rpc data" output="true" returntype="string">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="array" />
		<cfset var result = 0 />
		<cfset var x = 0 />
		
		<cfsavecontent variable="result">
			<array>
				<data>
					<cfloop from="1" to="#ArrayLen(arguments.data)#" index="x">
						#serialize(arguments.data[x], "any")#
					</cfloop>
				</data>
			</array>
		</cfsavecontent>
		
		<cfreturn result />
	</cffunction>
		
	<cffunction name="findMethodReturnType" access="public" hint="I get the type of data the method returns" output="false" returntype="string">
		<cfargument name="component" hint="I am the component to call methods on" required="yes" type="any" />
		<cfargument name="methodName" hint="I am the name of the method on the rpc component that is being called" required="yes" type="any" />
		<cfset var returnType = getMetadata(arguments.component[arguments.methodName]).returnType />
		
		<cfif ListFindNoCase("numeric,boolean,string,date,binary", returnType)>
			<cfreturn returnType />
		<cfelse>
			<cfreturn "any" />
		</cfif>
	</cffunction>
		
	<cffunction name="findMethodParams" access="private" hint="I translate a rpc request into an array of values to pass to a component" output="false" returntype="array">
		<cfargument name="rpc" hint="I am the xml data for the RPC" required="yes" type="xml" />
		<cfargument name="component" hint="I am the component to call methods on" required="yes" type="any" />
		<cfargument name="methodName" hint="I am the name of the method on the rpc component that is being called" required="yes" type="any" />
		<cfset var requiredParams = getMetadata(arguments.component[arguments.methodName]).parameters />
		<cfset var providedParams = arguments.rpc.methodCall.params.XmlChildren />
		<cfset var result = ArrayNew(1) />
		<cfset var param = 0 />
		<cfset var value = 0 />
		
		<!--- <cflog text="Method Name: #arguments.methodName#" /> --->
		<!--- <cflog text="Params: #ArrayLen(providedParams)#" /> --->
		
		<cfloop from="1" to="#ArrayLen(requiredParams)#" index="x">
			<cfset result[x] = StructNew() />
			<cfset result[x].name = requiredParams[x].name />
			
			<cfset value = XmlParse(providedParams[x]).param.value />
			<!--- <cflog text="Param Value #x#: #value#" /> --->
			
			<!--- I need to get children in some cases --->
			<cfif ArrayLen(value.XmlChildren)>
				<!--- this has a format of <value><string>foo</string></value> (where string could be whatever) --->
				<cfset result[x].value = deserialize(value.XmlChildren[1]) />
			<cfelse>
				<!--- this is a case where the <value /> tag doesn't have any child type identifier like <string />:  <value>foo</value> --->
				<cfset result[x].value = value.XmlText />
			</cfif>
			
			<!--- <cflog text="Param Value #x#: #result[x].value.toString()#" /> --->
			
		</cfloop>
		
		<cfreturn result />
	</cffunction>
	
	<cffunction name="deserialize" access="private" hint="I translate a rpc data into coldfusion data" output="false" returntype="any">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="xml" />
		
		<cfswitch expression="#arguments.data.XmlName#">
			<cfcase value="i4,int,boolean,double">
				<cfreturn val(arguments.data.XmlText) />
			</cfcase>
			<cfcase value="string">
				<cfreturn arguments.data.XmlText />
			</cfcase>
			<cfcase value="dateTime.iso8601">
				<cfreturn arguments.data.XmlText />
			</cfcase>
			<cfcase value="base64">
				<cfreturn ToBinary(arguments.data.XmlText) />
			</cfcase>
			<cfcase value="struct">
				<cfreturn deserializeStruct(arguments.data.XmlChildren) />
			</cfcase>
			<cfcase value="array">
				<cfreturn deserializeArray(arguments.data.XmlChildren[1]) />
			</cfcase>
			<cfcase value="value">
				<cfif isXml(arguments.data)>
					<cfreturn deserialize(arguments.data.XmlChildren[1]) />
				<cfelse>
					<cfreturn arguments.data.XmlText />
				</cfif>
			</cfcase>
		</cfswitch>
		
		<cfthrow message="Could Not Deserialize RPC Data" detail="The rpc data was identified as '#arguments.data.XmlName#' and is recognized." type="xmlrpc.rpcCouldNotDeserializeRpcData" />		
	</cffunction>
	
	<cffunction name="deserializeStruct" access="private" hint="I translate a rpc structure into coldfusion structure" output="false" returntype="struct">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="array" />
		<cfset var x = 0 />
		<cfset var result = StructNew() />
		<cfset var value = 0 />
		
		<cfloop from="1" to="#ArrayLen(data)#" index="x">
			<cfset result[data[x].name.XmlText] = deserialize(data[x].value.XmlChildren[1]) />
		</cfloop>
		
		<!--- <cflog text="#result.toString()#" /> --->
		
		<cfreturn result />	
	</cffunction>
	
	<cffunction name="deserializeArray" access="private" hint="I translate a rpc array into coldfusion array" output="false" returntype="array">
		<cfargument name="data" hint="I am the data to translate" required="yes" type="any" />
		<cfset var x = 0 />
		<cfset var result = ArrayNew(1) />
		
		<cfif ArrayLen(arguments.data.XmlChildren)>
			<cfloop from="1" to="#ArrayLen(arguments.data.XmlChildren)#" index="x">
				<cfset result[x] = deserialize(arguments.data.XmlChildren[x]) />
			</cfloop>
		</cfif>
		
		<cfreturn result />
	</cffunction>
	
	<cffunction name="findMethodNameUsingMetadata" access="private" hint="I find a method based on it's rpc name or it's actual name and it's remote status via a set of provided metadata" output="false" returntype="any">
		<cfargument name="rpcName" hint="I am the name of the rpc method to call" required="yes" type="string" />
		<cfargument name="metadata" hint="I am the component metadata to search" required="yes" type="any" />
		<cfset var x = 0 />
		
		<!--- look under the functions for the specific rpcName or the name --->
		<cfif StructKeyExists(arguments.metadata, "functions")>
			<cfloop from="1" to="#ArrayLen(arguments.metadata.functions)#" index="x">	
				<cfif arguments.metadata.functions[x].access IS "remote" AND (StructKeyExists(arguments.metadata.functions[x], "rpcName") AND arguments.metadata.functions[x].rpcName IS arguments.rpcName)
					OR arguments.metadata.functions[x].name IS arguments.rpcName>
					<cfreturn arguments.metadata.functions[x].name />					
				</cfif>
			</cfloop>
		</cfif>
		
		<cfif structKeyExists(arguments.metadata, "extends")>
			<cfreturn findMethodNameUsingMetadata(arguments.rpcName, arguments.metadata.extends) />
		</cfif>
		
		<cfthrow message="Could Not Find RPC Method #arguments.rpcName# " detail="A remote method with an rpc name of #arguments.rpcName# could not be found in the component. " type="xmlrpc.rpcMethodNotFound" />
		
	</cffunction>
	
	<cffunction name="matchRegex" access="private" hint="I am a function that can be used to more easily parse a set of matches in a particular way using regular expressions" output="false" returntype="string">
         <cfargument name="string" hint="I am the string to parse"  required="yes" type="string" />
         <cfargument name="regex" hint="I am the regex to use"  required="yes" type="string" />
         <cfargument name="callbackFunction" hint="I am the call back function to use. This must accept one argument, an array of elements in one match."  required="yes" type="string" />
         <cfset var location = 0 />
         <cfset var match = 0 />
         <cfset var full = 0 />
         <cfset var detail = 0 />
         <cfset var result = 0 />
         <cfset var x = 0 />
         <cfset var left = 0 />
         <cfset var right = 0 />
         <cfset var change = 0 />
        
         <cfloop condition="true">
               <cfset detail = ArrayNew(1) />
               <cfset match = ReFind(arguments.regex, arguments.string, location, true) />     

               <cfif match.len[1] IS 0>
                     <cfbreak />
               </cfif>

               <!--- parse the match into chunks --->
               <cfloop from="1" to="#ArrayLen(match.len)#" index="x">
                     <cfset detail[x] = StructNew() />
                     <cfif match.len[x]>
                           <cfset detail[x].string = Mid(arguments.string, match.pos[x], match.len[x]) />
                     <cfelse>
                           <cfset detail[x].string = "" />
                     </cfif>
                     <cfset detail[x].pos = match.pos[x] />
                     <cfset detail[x].len = match.len[x] />
               </cfloop>
              
               <cfif match.len[1]>
                     <cfset left = left(arguments.string, match.pos[1] - 1) />
                     <cfset right = right(arguments.string, Len(arguments.string) - (match.pos[1] + match.len[1]) + 1) />
                     <cfset change = arguments.callbackFunction(detail) />
                     <cfset arguments.string = left & change & right />
               </cfif>

               <cfset location = len(left) + len(change) + 1 />
         </cfloop>
       
         <cfreturn arguments.string />
      </cffunction>
	
</cfcomponent>
